package org.sxp.common.jwt;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.util.AntPathMatcher;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;
import org.sxp.common.exception.BusinessException;
import org.sxp.common.utils.SpringContextUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;

/**
 * @author shenxingping
 * @date 2021/02/03
 */
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {

    public static String JWT_TOKEN_KEY = "X-AUTH-TOKEN";
    /**
     * 果带有 token，则对 token 进行检查，否则直接通过
     * @author HCY
     * @param request
     * @param response
     * @param mappedValue
     * @return
     * @throws UnauthorizedException
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws UnauthorizedException {
        if(isLoginAttempt(request, response)){
            //如果存在，则进入 executeLogin 方法执行登入，检查 token 是否正确
            try {
                executeLogin(request, response);
                return true;
            } catch (Exception e) {
                throw new AuthorizationException(e.getMessage(), e);
            }
        }
        ShiroFilterFactoryBean filterFactoryBean = SpringContextUtils.getBean(ShiroFilterFactoryBean.class);
        HttpServletRequest servletRequest = (HttpServletRequest)request;
        HttpServletResponse servletResponse = (HttpServletResponse)response;
        String url = servletRequest.getRequestURI();
        AntPathMatcher pathMatcher = new AntPathMatcher();
        // 判断是否需要排除
        boolean boo = false;
        for(String key : filterFactoryBean.getFilterChainDefinitionMap().keySet()){
            String value = filterFactoryBean.getFilterChainDefinitionMap().get(key);
            if(pathMatcher.match(key, url) && value.equals("anon")){
                boo = true;
            }
        }
        if(url.equals(filterFactoryBean.getUnauthorizedUrl())){
            boo = true;
        }
        //判断是否登录请求页面
        if(url.equals(filterFactoryBean.getLoginUrl())){
            boo = true;
        }
        try {
            if(url.endsWith(".html")){
                servletResponse.sendRedirect(filterFactoryBean.getLoginUrl());
            }else{
                throw new BusinessException("用户未登录!");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        //如果请求头不存在 Token，则可能是执行登陆操作或者是游客状态访问，无需检查 token，直接返回 true
        return boo;
    }

    public static boolean isAjaxRequest(HttpServletRequest request) {
        String header = request.getHeader("X-Requested-With");
        return !StrUtil.isEmpty(header) && "XMLHttpRequest".equals(header);
    }

    /**
     * 执行登录的操作
     * @author HCY
     * @since 2020-10-11
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        String token = getHttpToken(request);
        JwtToken jwtToken = new JwtToken(token);
        // 提交给realm进行登入，如果错误他会抛出异常并被捕获
        getSubject(request, response).login(jwtToken);
        // 如果没有抛出异常则代表登入成功，返回true
        return true;
    }

    /**
     * 判断用户是否想要登入。
     * 检测 header 里面是否包含 Token 字段
     * @author HCY
     * @since 2020-10-11
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        return getHttpToken(request) != null;
    }

    private String getHttpToken(ServletRequest request){
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader(JWT_TOKEN_KEY);
        if(token != null){
            return token;
        }
        Cookie[] cookies = req.getCookies();
        if(cookies != null){
            for(Cookie cookie : cookies){
                if(cookie.getName().equals(JWT_TOKEN_KEY)){
                    return cookie.getValue();
                }
            }
        }
        return null;
    }

    /**
     * 对跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求，这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    /**
     * 将非法请求跳转到 /unauthorized/**
     */
    private void responseError(ServletResponse response, String message) {
        try {
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            //设置编码，否则中文字符在重定向时会变为空字符串
            message = URLEncoder.encode(message, "UTF-8");
            httpServletResponse.sendRedirect("/" + message);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
    }
}
